--
-- XC_CREATE_FUNCTIONS
--

-- Create a couple of functions used by Postgres-XL tests
-- A function to create table on specified nodes
create or replace function create_table_nodes(tab_schema varchar, nodenums int[], distribution varchar, cmd_suffix varchar)
returns void language plpgsql as $$
declare
    cr_command    varchar;
    nodes        varchar[];
    nodename    varchar;
    nodenames_query varchar;
    nodenames     varchar;
    node         int;
    sep            varchar;
    tmp_node    int;
    num_nodes    int;
begin
    nodenames_query := 'SELECT node_name FROM pgxc_node WHERE node_type = ''D''';
    cr_command := 'CREATE TABLE ' || tab_schema || ' DISTRIBUTE BY ' || distribution || ' TO NODE ';
    for nodename in execute nodenames_query loop
        nodes := array_append(nodes, nodename);
    end loop;
    nodenames := '(';
    sep := '';
    num_nodes := array_length(nodes, 1);
    foreach node in array nodenums loop
        tmp_node := node;
        if (tmp_node < 1 or tmp_node > num_nodes) then
            tmp_node := tmp_node % num_nodes;
            if (tmp_node < 1) then
                tmp_node := num_nodes; 
            end if;
        end if;
        nodenames := nodenames || sep || nodes[tmp_node];
        sep := ', ';
    end loop;
    nodenames := nodenames || ')';
    cr_command := cr_command || nodenames;
    if (cmd_suffix is not null) then
        cr_command := cr_command  || ' ' || cmd_suffix;
    end if;
    execute cr_command;
end;
$$;

-- Add/Delete/change node list of a table
CREATE OR REPLACE FUNCTION alter_table_change_nodes(tab_schema varchar, nodenums int[], command varchar, distribution varchar)
RETURNS BOOLEAN LANGUAGE plpgsql as $$
declare
    cr_command    varchar;
    nodes        varchar[];
    nodename    varchar;
    nodenames_query varchar;
    nodenames    varchar;
    sep        varchar;
    nodenum_new    int[];
    nodenum_res    int[];
    tmp_node    int;
    num_nodes    int;
    node        int;
    check_num    boolean;
    enforce_to    boolean;
BEGIN
    -- Check the command type, only delete/add/to are allowed
    IF command != 'delete' AND command != 'add' AND command != 'to' THEN
        RETURN FALSE;
    END IF;
    nodenames_query := 'SELECT node_name FROM pgxc_node WHERE node_type = ''D''';
    FOR nodename IN EXECUTE nodenames_query LOOP
        nodes := array_append(nodes, nodename);
    END LOOP;
    nodenames := '(';
    sep := '';
    num_nodes := array_length(nodes, 1);
    enforce_to := FALSE;

    -- Adjust node array according to total number of nodes
    FOREACH node IN ARRAY nodenums LOOP
        tmp_node := node;
        IF (node < 1 OR node > num_nodes) THEN
            -- Enforce the usage of TO here, only safe method
            enforce_to := TRUE;
            tmp_node := node % num_nodes;
            nodenum_new := array_append(nodenum_new, tmp_node);
        END IF;
        nodenum_new := array_append(nodenum_new, tmp_node);
    END LOOP;
    -- Eliminate duplicates
    nodenum_res := array_append(nodenum_res, nodenum_new[1]);
    FOREACH node IN ARRAY nodenum_new LOOP
        check_num := TRUE;
        FOREACH tmp_node IN ARRAY nodenum_res LOOP
            IF (tmp_node = node) THEN
                check_num := FALSE;
            END IF;
        END LOOP;
        -- Fill in result array only if not replicated
        IF check_num THEN
            nodenum_res := array_append(nodenum_res, node);
        END IF;
    END LOOP;

    -- If there is a unique Datanode in cluster, enforce the use of 'TO NODE'
    -- This will avoid any consistency problems
    IF (num_nodes = 1 OR enforce_to) THEN
        command := 'TO';
    END IF;

    -- Finally build query
    cr_command := 'ALTER TABLE ' || tab_schema || ' ' || command || ' NODE ';
    FOREACH node IN ARRAY nodenum_res LOOP
        IF (node > 0 AND node <= num_nodes) THEN
            nodenames := nodenames || sep || nodes[node];
            sep := ', ';
        END IF;
    END LOOP;
    nodenames := nodenames || ')';
    cr_command := cr_command || nodenames;

    -- Add distribution if necessary
    IF (distribution IS NOT NULL) then
        cr_command := cr_command  || ', DISTRIBUTE BY ' || distribution;
    END IF;

    -- Launch it
    EXECUTE cr_command;
    RETURN TRUE;
END;
$$;

-- A function to return data node name given a node number
CREATE OR REPLACE FUNCTION get_xc_node_name(node_num int) RETURNS varchar LANGUAGE plpgsql AS $$
DECLARE
    r        pgxc_node%rowtype;
    node        int;
    nodenames_query    varchar;
BEGIN
    nodenames_query := 'SELECT * FROM pgxc_node  WHERE node_type = ''D'' ORDER BY xc_node_id';

    node := 1;
    FOR r IN EXECUTE nodenames_query LOOP
        IF node = node_num THEN
            RETURN r.node_name;
        END IF;
        node := node + 1;
    END LOOP;
    RETURN 'NODE_?';
END;
$$;

-- A function to check whether a certain transaction was prepared on a specific data node given its number
CREATE OR REPLACE FUNCTION is_prepared_on_node(txn_id varchar, nodenum int) RETURNS bool LANGUAGE plpgsql AS $$
DECLARE
    nodename    varchar;
    qry        varchar;
    r        pg_prepared_xacts%rowtype;
BEGIN
    nodename := (SELECT get_xc_node_name(nodenum));
    qry := 'EXECUTE DIRECT ON (' || nodename || ') ' || chr(39) || 'SELECT * FROM pg_prepared_xacts' || chr(39);

    FOR r IN EXECUTE qry LOOP
        IF r.gid = txn_id THEN
            RETURN true;
        END IF;
    END LOOP;
    RETURN false;
END;
$$;

-- A function to execute direct a query on a data node specified by the node number.
create or replace function exec_util_on_node(query varchar, nodenum int) returns void as
$D$
DECLARE
    str varchar;
    node_name varchar;
BEGIN
    node_name = get_xc_node_name(nodenum);
    str = 'execute direct on (' || node_name || ') $$ ' || query || ' $$'  ;
    execute str;
END $D$ language plpgsql;
